#include "stdafx.h"
#include "4connected.h"
#include "cvplot.h"
#include "meanFilter.h"
#include "subContour.h"
#include "contour.h"
#include "filesFromDir.h"
#include "edgesAndDerivatives.h"
#include "bufferStruct.h"
#include "corners.h"
#include "regionCounter.h"
#include "helper.h"
#include "canny.h"
#include "types.h"
#include "localmaxima.h"
#include "interval.h"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <memory>
#include <omp.h>
#include <stdexcept>
using namespace cv;

/// Global variables

int frameH;// = 540;
int frameW;// = 960;
const int lowThreshold = 50; // canny 50
const int ratio = 6; //9
const int kernel_size = 3;

int blockSize = 4; // corners
double kEdges = 0.08; // 0.05
const double minMaxCorner = 0.04;

int maxHeight; // = 100;
int maxWidth;// = 50;
int minHeight;// = 4;
int minWidth;// = 4;
double minLength;// = 10.;
int maxStrokeSize;

const double minParallelPixels = 2. / 5;

int minSubContourLen;// = 3;
int minContourSubcontersNumber;// = 6;

int rectangleZoneWidth, rectangleZoneHeight;
int zonesNumberHeight, zonesNumberWidth;

const double unstaticContour = 0.6;

const double frameSimil = 0.6;
const double frameSimPerReg = 0.01;
const double newRegionsLimit = 0.1;

const double cornerMinPerReg = 0.002; // 0.0013
const double edgesPerRegion = 0.01; // 0.007


const double minAppearances = 0.65;
const double minExtremumK = 0.15;
const double minimumIndicator = 0.20;
const double minimumIndicatorVertical = 0.10;
const double verticalProjSpaceK = 3.2;
const double topOffset = 1 / 6.;
const double leftOffset = 0.7;
Mat blackM; // black matrix
double timer;

std::string baseDir = "C:\\Users\\John\\Desktop\\gr\\";
std::string outDir = "E:\\med2\\";
int outCounter = 0;
bool debug = 1;


std::vector<std::string> lFiles;
std::vector<bufferStruct> bufferOfFrames;
unsigned int frames;


void setRestrictions(){

	double ratioWH = ((double)frameW) / frameH;

	maxHeight = frameH / 8;
	maxWidth = (int)maxHeight * ratioWH;
	minHeight = frameH / 200;
	minWidth = (int)2 * minHeight * ratioWH / 3;
	minLength = frameH / 50;
	minSubContourLen = minLength / 5;
	minContourSubcontersNumber = 4;

	rectangleZoneHeight = maxHeight;
	rectangleZoneWidth = ((int)maxHeight * ratioWH);
	zonesNumberHeight = frameH / rectangleZoneHeight + (frameH % rectangleZoneHeight != 0);
	zonesNumberWidth = frameW / rectangleZoneWidth + (frameW % rectangleZoneWidth != 0);
	blackM = Mat(frameH, frameW, CV_8UC(1), Scalar::all(0));

	maxStrokeSize = rectangleZoneHeight / 9;
}

Mat mat2gray(const cv::Mat& src)
{
	Mat dst;
	normalize(src, dst, 0.0, 255.0, cv::NORM_MINMAX, CV_8U);

	return dst;
}

void findOrientedContours(const vector<vector<Point>>& orig, vector<vector<Point>>& contours, const Mat& map,
	vector<vector<vecSize>>& relContSubcont, vector<vecSize>& relSubcontCont,
	vector<SubContour>& subcInfo, vector<Contour>& contInfo){
	int counter = orig.size() - 1;

	for (auto contour2 = orig.rbegin(); contour2 != orig.rend(); ++contour2){
		auto& contour = *contour2;

		if (contInfo.at(counter).actual == false){
			counter--;
			continue;
		}


		Direction lastDir = (Direction)map.at<uchar>(contour.at(0).y, contour.at(0).x);
		Direction curDir;

		contours.push_back(vector<Point>{contour.at(0)});
		subcInfo.push_back(SubContour{ lastDir, 0, true });
		relContSubcont.at(counter).push_back(contours.size() - 1);
		relSubcontCont.push_back(counter);


		for (const Point& point : contour){
			curDir = (Direction)map.at<uchar>(point.y, point.x);
			if (curDir == lastDir){
				contours.at(contours.size() - 1).push_back(point);
			}
			else{
				contours.push_back(vector<Point>{point});
				subcInfo.push_back(SubContour{ curDir, 0, true });
				relContSubcont.at(counter).push_back(contours.size() - 1);
				relSubcontCont.push_back(counter);
			}
			lastDir = curDir;
		}

		counter--;
	}
}


int findHorizontalStroke(const vector<vecSize>& contour, const vector<SubContour>& subcInfo, const vector<vecSize>& relSubcontCont,
	const vector<vector<Point>>& subcontours, Mat&  phase, const vector<Point>& contourPoints){
	int max = 0;
	int counter = 0;
	bool onceMore = true;
	int prevx = -1, prevy = -1;
	int prevprevx = -1, prevprevy = -1;
	for (int i = 0; (i < contourPoints.size() || onceMore) && (i < 2 * contourPoints.size() - 1); i++){
		unsigned int index = i % contourPoints.size();
		float& angle = phase.at<float>(contourPoints.at(index));
		//(oriPixel > 67.5 && oriPixel <= 112.5) || (oriPixel > 247.5 && oriPixel <= 292.5)
		if (((angle >= 55 && angle <= 125) || (angle >= 235 && angle <= 305)) && (prevprevx != contourPoints.at(index).x || prevprevy != contourPoints.at(index).y)){
			counter++;
			onceMore = true;
		}
		else{
			max = std::max(max, counter);
			counter = 0;
			onceMore = false;
		}

		prevprevx = prevx;
		prevprevy = prevy;
		prevx = contourPoints.at(index).x;
		prevy = contourPoints.at(index).y;
	}


	return max;

}

void findParallelLines(const vector<vector<Point>>& contours,
	const vector<vector<vecSize>>& relContSubcont, const vector<vecSize>& relSubcontCont,
	vector<SubContour>& subcInfo, vector<Contour>& contInfo, Mat&  phase, vector<vector<Point>>& subcontours, const Mat& oriMap){
	//int i = 0;
	const uchar** poriMap = new const uchar*[frameH];
	for (int i = 0; i < frameH; i++){
		poriMap[i] = oriMap.ptr<uchar>(i);
	}

	omp_set_num_threads(4);
#pragma omp parallel for
	for (int i = 0; i < relContSubcont.size(); i++){
		const auto& cont = relContSubcont[i];

		if (cont.size() < minContourSubcontersNumber){
			contInfo.at(i).actual = false;
		}

		if (contInfo.at(i).actual == false){
			//i++;
			continue;
		}

		//if (i == 190){
		//	std::cout << "";
		//}

		int strokeWidth = 15 * findHorizontalStroke(cont, subcInfo, relSubcontCont, subcontours, phase, contours.at(i)) / 10;
		int strokeWidthBasedOnCont = 5 * contInfo.at(i).height / 15;
		strokeWidth = std::min(strokeWidth, strokeWidthBasedOnCont);
		strokeWidth = std::min(maxStrokeSize, strokeWidth);
		//strokeWidth = std::min
		//std::cout << strokeWidth << std::endl;
		for (const auto& scont : cont){
			const SubContour& sInfo = subcInfo.at(scont);
			const auto& points = subcontours.at(scont);

			if (sInfo.dir == FIRST){
				for (const auto& point : points){
					int left = std::max(0, point.x - strokeWidth);
					int right = std::min(point.x + strokeWidth, frameW - 1);
					for (int cleft = left; cleft <= right; cleft++){
						if (poriMap[point.y][cleft] == FIRST && cleft != point.x){
							contInfo[i].counter++;
							break;
						}
					}
				}
			}

			if (sInfo.dir == THIRD){
				for (const auto& point : points){
					int top = std::max(0, (int)point.y - strokeWidth);
					int bottom = std::min((int)point.y + strokeWidth, frameH - 1);
					for (int ctop = top; ctop <= bottom; ctop++){
						if (poriMap[ctop][point.x] == THIRD && ctop != point.y){
							contInfo[i].counter++;
							break;
						}
					}
				}
			}

			if (sInfo.dir == FOURTH){
				for (const auto& point : points){
					int left = 0;
					int strokeWidth2 = 2 * strokeWidth / 3;
					if (std::min(point.x - strokeWidth2, point.y - strokeWidth2) >= 0){
						left = point.x - strokeWidth2;
					}
					else{
						left = point.x - std::min(point.x, point.y);
					}
					int right = 0;
					if (std::min(frameW - 1 - point.x - strokeWidth2, frameH - 1 - point.y - strokeWidth2) >= 0){
						right = point.x + strokeWidth2;
					}
					else{
						right = point.x - std::min(frameW - 1 - point.x, frameH - 1 - point.y);
					}
					for (int cleft = left; cleft <= right; cleft++){
						if (poriMap[cleft - point.x + point.y][cleft] == FOURTH && cleft != point.x){
							contInfo[i].counter++;
							break;
						}
					}
				}
			}

			if (sInfo.dir == SECOND){
				for (const auto& point : points){
					int left = 0;
					int strokeWidth2 = 2 * strokeWidth / 3;
					if (std::min(point.x - strokeWidth2, frameH - 1 - point.y - strokeWidth2) >= 0){
						left = point.x - strokeWidth2;
					}
					else{
						left = point.x - std::min(point.x, frameH - 1 - point.y);
					}
					int right = 0;
					if (std::min(frameW - 1 - point.x - strokeWidth2, point.y - strokeWidth2) >= 0){
						right = point.x + strokeWidth2;
					}
					else{
						right = point.x - std::min(frameW - 1 - point.x, point.y);
					}
					for (int cleft = left; cleft <= right; cleft++){
						if (poriMap[-cleft + point.x + point.y][cleft] == SECOND && cleft != point.x){
							contInfo[i].counter++;
							break;
						}
					}
				}
			}


		}

		if (((double)contInfo.at(i).counter) / contInfo.at(i).perimeter < minParallelPixels){
			contInfo.at(i).actual = false;
		}

		//i++;
	}

	delete[] poriMap;
}

void filterContours(vector<vector<Point>>& orig, vector<Contour>& contInfo){
	int i = 0;

	auto deleteIterator = orig.begin();
	while (deleteIterator != orig.end()) {
		Rect  rec = boundingRect(Mat(*deleteIterator));
		int arc = deleteIterator->size();
		if (rec.width >= maxWidth || rec.height >= maxHeight || rec.width <= minWidth || rec.height <= minHeight || arc < minLength){
			deleteIterator = orig.erase(deleteIterator);
		}
		else{
			contInfo.push_back(Contour{ 0, true, rec.width, rec.height, arc });
			deleteIterator++;
		}

	}


}

void subContInfo(vector<SubContour>& info, const vector<vector<Point>>& contours2){
	int i = 0;
	for (const vector<Point>& vec : contours2){
		//if (vec.size() <= minSubContourLen){
		//	info.at(i).useful = false;
		//}
		//else{
		Rect  rec = boundingRect(vec);
		info.at(i).height = rec.height;
		info.at(i).width = rec.width;
		info.at(i).perimeter = vec.size();
		info.at(i).x = rec.x;
		info.at(i).y = rec.y;
		//}

		i++;
	}
}

void filterUnstatic(vector<vector<Point>>& orig, vector<Contour>& contInfo, const std::vector<edgesAndDerivatives>& cannyBuffer){
	Mat prevprev = cannyBuffer.at(0).edges, prev = cannyBuffer.at(1).edges,
		next = cannyBuffer.at(3).edges, nextnext = cannyBuffer.at(4).edges;
	int i = 0;
	auto deleteIterator = orig.begin();
	while (deleteIterator != orig.end()){
		unsigned int prevprevcounter = 0, prevcounter = 0, nextcounter = 0, nextnextcounter = 0;
		for (const auto& point : *deleteIterator){
			if (prevprev.at<uchar>(point.y, point.x) == 255){
				prevprevcounter++;
			}
			if (prev.at<uchar>(point.y, point.x) == 255){
				prevcounter++;
			}
			if (next.at<uchar>(point.y, point.x) == 255){
				nextcounter++;
			}
			if (nextnext.at<uchar>(point.y, point.x) == 255){
				nextnextcounter++;
			}
		}
		unsigned int limit = unstaticContour * contInfo.at(i).perimeter;
		if ((prevprevcounter >= limit && prevcounter >= limit) || (prevcounter >= limit && nextcounter >= limit)
			|| (nextnextcounter >= limit && nextcounter >= limit)){
		}
		else{
			contInfo.at(i).actual = false;
		}
		deleteIterator++;
		i++;
	}
}

Mat orientationMap(const cv::Mat& ori, const cv::Mat& canny,
	const vector<vector<Point>>& contours, const vector<Contour>& contInfo)
{
	Mat oriMap = Mat(ori.size(), CV_8U, Scalar::all(NOTEDGE));
	int i = 0;
	for (const auto& cont : contours)
	{
		if (contInfo.at(i).actual == false){
			i++;
			continue;
		}

		//float* magPixel = reinterpret_cast<float*>(mag.data + i*sizeof(float));
		for (const auto& point : cont){
			const float& oriPixel = ori.at<float>(point.y, point.x);
			if ((oriPixel <= 22.5 && oriPixel >= 0) || (oriPixel > 157.5 && oriPixel <= 202.5) || (oriPixel > 337.5)){
				oriMap.at<uchar>(point.y, point.x) = FIRST;
				//dir1.at(i / frameW).at(i % frameW) = true;
			}
			else if ((oriPixel > 22.5 && oriPixel <= 67.5) || (oriPixel > 202.5 && oriPixel <= 247.5)){
				oriMap.at<uchar>(point.y, point.x) = SECOND;
				//dir2.at(i / frameW).at(i % frameW) = true;
			}
			else if ((oriPixel > 67.5 && oriPixel <= 112.5) || (oriPixel > 247.5 && oriPixel <= 292.5)){
				oriMap.at<uchar>(point.y, point.x) = THIRD;
				//dir3.at(i / frameW).at(i % frameW) = true;
			}
			else if ((oriPixel > 112.5 && oriPixel <= 157.5) || (oriPixel > 292.5 && oriPixel <= 337.5)){
				oriMap.at<uchar>(point.y, point.x) = FOURTH;
				//dir4.at(i / frameW).at(i % frameW) = true;
			}

		}
		i++;
	}


	return oriMap;
}

void canny(const Mat& srcImg, Mat& detected_edges, Mat& dx, Mat& dy, Mat& gray){
	/// Convert the image to grayscale
	cvtColor(srcImg, gray, CV_BGR2GRAY);
	blur(gray, detected_edges, Size(3, 3));
	/// Canny detector
	//Canny(detected_edges, detected_edges, lowThreshold, lowThreshold*ratio, kernel_size);
	const int cn = srcImg.channels();
	dx = Mat(srcImg.rows, srcImg.cols, CV_16SC(cn));
	dy = Mat(srcImg.rows, srcImg.cols, CV_16SC(cn));

	// result
	CannyModified(detected_edges, detected_edges, dx, dy, lowThreshold, lowThreshold*ratio, kernel_size, false);

	dx.convertTo(dx, CV_32F); // dxFloat
	dy.convertTo(dy, CV_32F); // dyFloat

}

void generateCanny(int num, std::vector<edgesAndDerivatives>& cannyBuffer){
	if (num == 0){
		//Mat blackM = Mat(frameH, frameW, CV_8UC(1), Scalar::all(0));
		edgesAndDerivatives black = edgesAndDerivatives{ blackM, blackM, blackM, blackM, blackM };
		cannyBuffer.at(0) = black;
		cannyBuffer.at(1) = black;

		Mat detected_edges, dx, dy, img, gray;
		img = imread(baseDir + lFiles.at(num));
		canny(img, detected_edges, dx, dy, gray);
		cannyBuffer.at(2) = { img, detected_edges, dx, dy, gray };

		Mat detected_edges2, dx2, dy2, img2, gray2;
		img2 = imread(baseDir + lFiles.at(num + 1));
		canny(img2, detected_edges2, dx2, dy2, gray2);
		cannyBuffer.at(3) = { img2, detected_edges2, dx2, dy2, gray2 };

	}
	if (num != frames - 2 && num != frames - 1){
		Mat detected_edges, dx, dy, img, gray;
		img = imread(baseDir + lFiles.at(num + 2));
		canny(img, detected_edges, dx, dy, gray);
		cannyBuffer.push_back({ img, detected_edges, dx, dy, gray });
	}
	else{
		//Mat blackM = Mat(frameH, frameW, CV_8UC(1), Scalar::all(0));
		edgesAndDerivatives black = edgesAndDerivatives{ blackM, blackM, blackM, blackM, blackM };
		cannyBuffer.push_back(black);
	}
}

void findTextRegions(const Mat& edges, const Mat& corners, std::shared_ptr<Regions>& textRegion,
	Mat& cornersBool, int& regNumber){

	double min, max;
	minMaxIdx(corners, &min, &max);
	max = minMaxCorner*max;
	double t = omp_get_wtime();
	Regions& ptextRegion = *textRegion.get();
	int zonesPerThread = zonesNumberHeight / 4 + (zonesNumberHeight % 4 != 0);
	omp_set_num_threads(4);
#pragma omp parallel  sections 
	{
#pragma omp section
		{
			int min = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = 0; i < min; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}

#pragma omp section
		{
		int min1 = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
		int min2 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
		const uchar* pedges;
		uchar* pcornersBool;
		const float* pcorners;
		for (int i = min1; i < min2; i++){
			int horIndex = i / rectangleZoneHeight;
			pedges = edges.ptr<uchar>(i);
			pcorners = corners.ptr<float>(i);
			pcornersBool = cornersBool.ptr<uchar>(i);
			for (int j = 0; j < frameW; j++){
				if (pedges[j] != NOTEDGE){
					ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
				}

				if (pcorners[j] >= max){
					ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
					pcornersBool[j] = 255;
				}
			}
		}
	}

#pragma omp section
		{
			int min1 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}
#pragma omp section
		{
			int min1 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(4 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}
	}

	int blockWidth, blockHeight, area;
	regNumber = 0;
	for (int i = 0; i < zonesNumberHeight; i++)
	{
		for (int j = 0; j < zonesNumberWidth; j++){
			blockWidth = std::min((frameW - j*rectangleZoneWidth), rectangleZoneWidth);
			blockHeight = std::min((frameH - i*rectangleZoneHeight), rectangleZoneHeight);
			area = blockWidth*blockHeight;
			textRegion->at(i).at(j).y = i*blockHeight;
			textRegion->at(i).at(j).x = j*blockWidth;
			textRegion->at(i).at(j).height = blockHeight;
			textRegion->at(i).at(j).width = blockWidth;
			textRegion->at(i).at(j).area = area;
			if (textRegion->at(i).at(j).corners > cornerMinPerReg*area && textRegion->at(i).at(j).edges > edgesPerRegion*area){
				textRegion->at(i).at(j).isText = true;
				regNumber++;
			}
		}
	}
	std::cout << "Time: " << omp_get_wtime() - t << std::endl;
}


void findTextRegionsEdges(const Mat& edges, std::shared_ptr<Regions>& textRegion, int& regNumber){


	double t = omp_get_wtime();
	Regions& ptextRegion = *textRegion.get();
	int zonesPerThread = zonesNumberHeight / 4 + (zonesNumberHeight % 4 != 0);
	omp_set_num_threads(4);
#pragma omp parallel  sections 
	{
#pragma omp section
		{
			int min = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			for (int i = 0; i < min; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}
				}
			}
		}

#pragma omp section
		{
		int min1 = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
		int min2 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
		const uchar* pedges;
		for (int i = min1; i < min2; i++){
			int horIndex = i / rectangleZoneHeight;
			pedges = edges.ptr<uchar>(i);
			for (int j = 0; j < frameW; j++){
				if (pedges[j] != NOTEDGE){
					ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
				}

			}
		}
	}

#pragma omp section
		{
			int min1 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}

				}
			}
		}
#pragma omp section
		{
			int min1 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(4 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			const uchar* pedges;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pedges = edges.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pedges[j] != NOTEDGE){
						ptextRegion[horIndex][j / rectangleZoneWidth].edges++;
					}

				}
			}
		}
	}

	int blockWidth, blockHeight, area;
	regNumber = 0;
	for (int i = 0; i < zonesNumberHeight; i++)
	{
		for (int j = 0; j < zonesNumberWidth; j++){
			blockWidth = std::min((frameW - j*rectangleZoneWidth), rectangleZoneWidth);
			blockHeight = std::min((frameH - i*rectangleZoneHeight), rectangleZoneHeight);
			area = blockWidth*blockHeight;
			textRegion->at(i).at(j).y = i*blockHeight;
			textRegion->at(i).at(j).x = j*blockWidth;
			textRegion->at(i).at(j).height = blockHeight;
			textRegion->at(i).at(j).width = blockWidth;
			textRegion->at(i).at(j).area = area;
			if (textRegion->at(i).at(j).edges > edgesPerRegion*area){
				textRegion->at(i).at(j).isText = true;
				regNumber++;
			}
		}
	}
	std::cout << "Time: " << omp_get_wtime() - t << std::endl;
}


void findTextRegionsCorners(const Mat& corners, std::shared_ptr<Regions>& textRegion, Mat& cornersBool, int& regNumber){

	double min, max;
	minMaxIdx(corners, &min, &max);
	max = minMaxCorner*max;
	double t = omp_get_wtime();
	Regions& ptextRegion = *textRegion.get();
	int zonesPerThread = zonesNumberHeight / 4 + (zonesNumberHeight % 4 != 0);
	omp_set_num_threads(4);
#pragma omp parallel  sections 
	{
#pragma omp section
		{
			int min = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = 0; i < min; i++){
				int horIndex = i / rectangleZoneHeight;
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}

#pragma omp section
		{
		int min1 = std::min(zonesPerThread*rectangleZoneHeight, frameH - 1);
		int min2 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
		uchar* pcornersBool;
		const float* pcorners;
		for (int i = min1; i < min2; i++){
			int horIndex = i / rectangleZoneHeight;
			pcorners = corners.ptr<float>(i);
			pcornersBool = cornersBool.ptr<uchar>(i);
			for (int j = 0; j < frameW; j++){

				if (pcorners[j] >= max){
					ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
					pcornersBool[j] = 255;
				}
			}
		}
	}

#pragma omp section
		{
			int min1 = std::min(2 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}
#pragma omp section
		{
			int min1 = std::min(3 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			int min2 = std::min(4 * zonesPerThread*rectangleZoneHeight, frameH - 1);
			uchar* pcornersBool;
			const float* pcorners;
			for (int i = min1; i < min2; i++){
				int horIndex = i / rectangleZoneHeight;
				pcorners = corners.ptr<float>(i);
				pcornersBool = cornersBool.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){

					if (pcorners[j] >= max){
						ptextRegion[horIndex][j / rectangleZoneWidth].corners++;
						pcornersBool[j] = 255;
					}
				}
			}
		}
	}

	int blockWidth, blockHeight, area;
	regNumber = 0;
	for (int i = 0; i < zonesNumberHeight; i++)
	{
		for (int j = 0; j < zonesNumberWidth; j++){
			int area = textRegion->at(i).at(j).height*textRegion->at(i).at(j).width;
			if (textRegion->at(i).at(j).corners > cornerMinPerReg*area && textRegion->at(i).at(j).isText){
				regNumber++;
			}
			else{
				textRegion->at(i).at(j).isText = false;
			}
		}
	}
	std::cout << "Time: " << omp_get_wtime() - t << std::endl;
}

void contours2TextEdges(const vector<vector<Point>>& contours, const vector<Contour>& contInfo, const Mat& directions,
	Mat& textEdges, Mat& boolEdges){
	int i = 0;
	for (const vector<Point>& contour : contours){
		if (contInfo.at(i).actual == true){
			for (const Point& point : contour){
				textEdges.at<uchar>(point.y, point.x) = directions.at<uchar>(point.y, point.x);
				boolEdges.at<uchar>(point.y, point.x) = CONTOUR;
			}
		}
		i++;
	}
}

void onlyInterestingPoints(const vector<vector<Point>>& contours, const vector<Contour>& contInfo,
	std::shared_ptr<std::vector<Point>>& interestingPoints, const std::shared_ptr<Regions>& textRegion){
	//int i = 0;
	for (int i = 0; i < contours.size(); i++){
		const auto& subc = contours[i];
		if (contInfo.at(i).actual != true){
			//i++;
			continue;
		}
		for (const auto& pt : subc){
			if (textRegion->at(pt.y / rectangleZoneHeight).at(pt.x / rectangleZoneWidth).isText == true){
				interestingPoints->push_back(pt);
			}
		}
		//i++;
	}

}

bool compareFrames(const std::shared_ptr<std::vector<Point>>& interestingPoints, const Mat& textEdges, std::shared_ptr<Regions> regs, int regNumber,
	const std::shared_ptr<std::vector<Point>>& prevInterestingPoints, const Mat& prevTextEdges, std::shared_ptr<Regions> prevRegs, int prevRegNumber){


	if (prevRegNumber == 0 && regNumber == 0){
		return true; // no regions in both frames
	}

	if (prevRegNumber != 0 && regNumber == 0 || prevRegNumber == 0 && regNumber != 0){
		return false; // no regions in one frame
	}

	//int simRegCounter = 0;
	//unsigned int edgesInRegs = 0;
	//for (int i = 0; i < zonesNumberHeight; i++){
	//	for (int j = 0; j < zonesNumberWidth; j++){
	//		if ((*regs)[i][j].isText == true && (*prevRegs)[i][j].isText == true){
	//			simRegCounter++;
	//		}
	//		else{
	//			if ((*regs)[i][j].isText == true && (*prevRegs)[i][j].isText != true){
	//				edgesInRegs += (*regs)[i][j].edges;
	//			}

	//			if ((*regs)[i][j].isText != true && (*prevRegs)[i][j].isText == true){
	//				edgesInRegs += (*prevRegs)[i][j].edges;
	//			}
	//		}
	//	}
	//}

	//if (edgesInRegs / (double)std::min((*prevInterestingPoints).size(), (*interestingPoints).size()) > newRegionsLimit){
	//	return false;
	//}


	//int i = 0, useful = 0;
	//for (const auto& pt : *interestingPoints){
	//	if (textEdges.at<uchar>(pt.y, pt.x) == prevTextEdges.at<uchar>(pt.y, pt.x)){
	//		useful++;
	//	}
	//	i++;
	//}

	//if (useful / ((double)i) < frameSimil - frameSimPerReg*simRegCounter){
	//	return false;
	//}

	//i = useful = 0;
	//for (const auto& pt : *prevInterestingPoints){
	//	if (textEdges.at<uchar>(pt.y, pt.x) == prevTextEdges.at<uchar>(pt.y, pt.x)){
	//		useful++;
	//	}
	//	i++;
	//}

	//if (useful / ((double)i) < frameSimil - frameSimPerReg*simRegCounter){
	//	return false;
	//}

	// some cache for the fast access
	Regions& pRegs = *(regs.get());
	Regions& pPrevRegs = *(prevRegs.get());
	const uchar** ptextEdges = new const uchar*[frameH];
	const uchar** pprevTextEdges = new const uchar*[frameH];
	for (int i = 0; i < frameH; i++){
		ptextEdges[i] = textEdges.ptr<uchar>(i);
		pprevTextEdges[i] = prevTextEdges.ptr<uchar>(i);
	}

	// init
	for (int i = 0; i < zonesNumberHeight; i++){
		for (int j = 0; j < zonesNumberWidth; j++){
			(*regs)[i][j].edgesUseful = 0;
			(*regs)[i][j].label = -1;
		}
	}

	// counting edges in regions
	for (const auto& pt : *interestingPoints){
		if (ptextEdges[pt.y][pt.x] == pprevTextEdges[pt.y][pt.x]){
			pRegs[pt.y / rectangleZoneHeight][pt.x / rectangleZoneWidth].edgesUseful++;
		}
	}

	// counting edges thats located in frames that don't match
	unsigned int contOut = 0;
	for (int i = 0; i < zonesNumberHeight; i++){
		for (int j = 0; j < zonesNumberWidth; j++){
			if ((*regs)[i][j].isText && (*regs)[i][j].edgesUseful / (double)(*regs)[i][j].edges < frameSimil){
				contOut += (*regs)[i][j].edges - (*regs)[i][j].edgesUseful;
			}
		}
	}

	// counting edges in connected regions
	Labels ls = find_components(*regs);
	for (const auto& group : ls){
		unsigned int edgesAll = 0;
		unsigned int edgesUseful = 0;
		for (const auto& block : group){
			edgesUseful += (*regs)[block.first][block.second].edgesUseful;
			edgesAll += (*regs)[block.first][block.second].edges;
		}
		// if number of pixel in entire region is great than 0.1
		if (edgesUseful / (double)edgesAll < frameSimil && edgesAll / (double)(*interestingPoints).size() > newRegionsLimit){
			delete[] ptextEdges;
			delete[] pprevTextEdges;
			return false;
		}
	}

	// look how much edges that have not pair exist
	if (contOut / (double)(*interestingPoints).size() > newRegionsLimit){
		delete[] ptextEdges;
		delete[] pprevTextEdges;
		return false;
	}

	///////////////////

	for (int i = 0; i < zonesNumberHeight; i++){
		for (int j = 0; j < zonesNumberWidth; j++){
			(*prevRegs)[i][j].edgesUseful = 0;
			(*prevRegs)[i][j].label = -1;
		}
	}
	for (const auto& pt : *prevInterestingPoints){
		if (ptextEdges[pt.y][pt.x] == pprevTextEdges[pt.y][pt.x]){
			pPrevRegs[pt.y / rectangleZoneHeight][pt.x / rectangleZoneWidth].edgesUseful++;
		}
	}
	contOut = 0;
	for (int i = 0; i < zonesNumberHeight; i++){
		for (int j = 0; j < zonesNumberWidth; j++){
			if ((*prevRegs)[i][j].isText && (*prevRegs)[i][j].edgesUseful / (double)(*prevRegs)[i][j].edges < frameSimil){
				contOut += (*prevRegs)[i][j].edges - (*prevRegs)[i][j].edgesUseful;
			}
		}
	}

	ls = find_components(*prevRegs);
	for (const auto& group : ls){
		unsigned int edgesAll = 0;
		unsigned int edgesUseful = 0;
		for (const auto& block : group){
			edgesUseful += (*prevRegs)[block.first][block.second].edgesUseful;
			edgesAll += (*prevRegs)[block.first][block.second].edges;
		}
		if (edgesUseful / (double)edgesAll < frameSimil && edgesAll / (double)(*prevInterestingPoints).size() > newRegionsLimit){
			delete[] ptextEdges;
			delete[] pprevTextEdges;
			return false;
		}
	}

	if (contOut / (double)(*prevInterestingPoints).size() > newRegionsLimit){
		delete[] ptextEdges;
		delete[] pprevTextEdges;
		return false;
	}

	return true;


}

void getHorizProjectionData(Projection& horizProjection, const Mat& image, const Mat& orientFirst, const Mat& orientLast){
	int linesPerThread = image.rows / 4 + (image.rows % 4 != 0);
	omp_set_num_threads(4);
#pragma omp parallel  sections 
	{
#pragma omp section
		{
			int min = std::min(linesPerThread, frameH - 1);
			const uchar* pim, *porientFirst, *porientLast;
			for (int i = 0; i < min; i++){
				int counter = 0;
				pim = image.ptr<uchar>(i);
				porientFirst = orientFirst.ptr<uchar>(i);
				porientLast = orientLast.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pim[j] != 0 && (porientFirst[j] == FIRST || porientLast[j] == FIRST)){
						counter++;
					}
					horizProjection[i][j] = counter;
				}
			}
		}

#pragma omp section
		{
		int min1 = std::min(linesPerThread, frameH - 1);
		int min2 = std::min(2 * linesPerThread, frameH - 1);
		const uchar* pim, *porientFirst, *porientLast;
		for (int i = min1; i < min2; i++){
			int counter = 0;
			pim = image.ptr<uchar>(i);
			porientFirst = orientFirst.ptr<uchar>(i);
			porientLast = orientLast.ptr<uchar>(i);
			for (int j = 0; j < frameW; j++){
				if (pim[j] != 0 && (porientFirst[j] == FIRST || porientLast[j] == FIRST)){
					counter++;
				}
				horizProjection[i][j] = counter;
			}
		}
	}

#pragma omp section
		{
			int min1 = std::min(2 * linesPerThread, frameH - 1);
			int min2 = std::min(3 * linesPerThread, frameH - 1);
			const uchar* pim, *porientFirst, *porientLast;
			for (int i = min1; i < min2; i++){
				int counter = 0;
				pim = image.ptr<uchar>(i);
				porientFirst = orientFirst.ptr<uchar>(i);
				porientLast = orientLast.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pim[j] != 0 && (porientFirst[j] == FIRST || porientLast[j] == FIRST)){
						counter++;
					}
					horizProjection[i][j] = counter;
				}
			}
		}

#pragma omp section
		{
			int min1 = std::min(3 * linesPerThread, frameH - 1);
			int min2 = std::min(4 * linesPerThread, frameH - 1);
			const uchar* pim, *porientFirst, *porientLast;
			for (int i = min1; i < min2; i++){
				int counter = 0;
				pim = image.ptr<uchar>(i);
				porientFirst = orientFirst.ptr<uchar>(i);
				porientLast = orientLast.ptr<uchar>(i);
				for (int j = 0; j < frameW; j++){
					if (pim[j] != 0 && (porientFirst[j] == FIRST || porientLast[j] == FIRST)){
						counter++;
					}
					horizProjection[i][j] = counter;
				}
			}
		}
	}

}

void getVertProjectionData(Projection& vertProjection, const Mat& image, const Mat& orientFirst, const Mat& orientLast)
{
	int linesPerThread = image.cols / 4 + (image.cols % 4 != 0);

	const uchar**  pimage = new const uchar*[frameH];
	const uchar** porientFirst = new const uchar*[frameH];
	const uchar** porientLast = new const uchar*[frameH];
	for (int i = 0; i < frameH; i++){
		pimage[i] = image.ptr<uchar>(i);
		porientFirst[i] = orientFirst.ptr<uchar>(i);
		porientLast[i] = orientLast.ptr<uchar>(i);
	}

	omp_set_num_threads(4);
#pragma omp parallel  sections 
	{
#pragma omp section
		{
			int min = std::min(linesPerThread, frameW - 1);
			for (int i = 0; i < min; i++){
				int counter = 0;
				for (int j = 0; j < frameH; j++){
					if (pimage[j][i] != 0 && (porientFirst[j][i] != FIRST || porientLast[j][i] != FIRST)){
						counter++;
					}
					vertProjection[j][i] = counter;
				}
			}
		}

#pragma omp section
		{
		int min1 = std::min(linesPerThread, frameW - 1);
		int min2 = std::min(2 * linesPerThread, frameW - 1);
		for (int i = min1; i < min2; i++){
			int counter = 0;
			for (int j = 0; j < frameH; j++){
				if (pimage[j][i] != 0 && (porientFirst[j][i] != FIRST || porientLast[j][i] != FIRST)){
					counter++;
				}
				vertProjection[j][i] = counter;
			}
		}
	}

#pragma omp section
		{
			int min1 = std::min(2 * linesPerThread, frameW - 1);
			int min2 = std::min(3 * linesPerThread, frameW - 1);
			for (int i = min1; i < min2; i++){
				int counter = 0;
				for (int j = 0; j < frameH; j++){
					if (pimage[j][i] != 0 && (porientFirst[j][i] != FIRST || porientLast[j][i] != FIRST)){
						counter++;
					}
					vertProjection[j][i] = counter;
				}
			}
		}

#pragma omp section
		{
			int min1 = std::min(3 * linesPerThread, frameW - 1);;
			int min2 = std::min(4 * linesPerThread, frameW - 1);
			for (int i = min1; i < min2; i++){
				int counter = 0;
				for (int j = 0; j < frameH; j++){
					if (pimage[j][i] != 0 && (porientFirst[j][i] != FIRST || porientLast[j][i] != FIRST)){
						counter++;
					}
					vertProjection[j][i] = counter;
				}
			}
		}
	}

	delete[] pimage;
	delete[] porientFirst;
	delete[] porientLast;
}

void textRegionsComposition(Regions& textRegion){
	for (int i = 0; i < zonesNumberHeight; i++){
		for (int j = 0; j < zonesNumberWidth; j++){
			int counter = 0;
			for (int frameN = 0; frameN < bufferOfFrames.size() - 1; frameN++){
				if (bufferOfFrames.at(frameN).regions->at(i).at(j).isText){
					counter++;
				}
			}

			textRegion.at(i).at(j) = bufferOfFrames.at(0).regions->at(i).at(j); // copy
			textRegion.at(i).at(j).label = -1;
			if ((float)counter / (bufferOfFrames.size() - 1) >= minAppearances){
				textRegion.at(i).at(j).isText = true;
			}
			else{
				textRegion.at(i).at(j).isText = false;
			}
		}
	}
}

std::vector<cv::Rect> generateInitialBoundingBoxes(const Regions& regions, const Labels& labels){
	std::vector<cv::Rect> res;
	for (const auto& label : labels){
		int left = frameW, right = 0, top = frameH, bottom = 0;
		for (const auto& region : label){
			int l, r, t, b;
			if ((l = regions.at(region.first).at(region.second).x) < left){
				left = l;
			}
			if ((r = regions.at(region.first).at(region.second).x + regions.at(region.first).at(region.second).width) > right){
				right = r;
			}
			if ((t = regions.at(region.first).at(region.second).y) < top){
				top = t;
			}
			if ((b = regions.at(region.first).at(region.second).y + regions.at(region.first).at(region.second).height) > bottom){
				bottom = b;
			}
		}
		res.push_back(cv::Rect(left, top, right - left, bottom - top));

	}
	return res;
}

std::vector<Interval> expandingBoxesHorizontal(std::vector<LocalMax>& maximus, cv::Rect box, const double* projValues,
	unsigned int padding, int boxHeight){
	std::vector<Interval> out;
	std::sort(maximus.begin(), maximus.end());
	for (const auto& max : maximus){
		double check = max.Data * minimumIndicator;
		if (contains(out, max.Idx)){
			continue;
		}
		// top
		unsigned int cur = max.Idx - 1;
		int top = -1;
		while (true){
			if (cur <= 0 || projValues[cur] < check || contains(out, cur)){
				top = cur;
				break;
			}
			cur--;
		}

		// bottom
		cur = max.Idx + 1;
		int bottom = -1;
		while (true){
			if (cur >= boxHeight + padding - 1 || projValues[cur] < check || contains(out, cur)){
				bottom = cur;
				break;
			}
			cur++;
		}
		int height = bottom - top;
		top = std::max(0, top - (int)(height*topOffset));
		bottom = std::min(boxHeight + (int)padding - 1, bottom + (int)(height*topOffset));
		out.push_back({ top, bottom });
		std::cout << top << "," << bottom << std::endl;

	}
	return out;
}

std::vector<Interval> expandingBoxesVertical(std::vector<LocalMax>& maximus, cv::Rect box, const double* projValues,
	unsigned int padding, int boxWidth, int boxHeight){
	std::vector<Interval> out;
	std::sort(maximus.begin(), maximus.end());
	for (const auto& max : maximus){
		double check = max.Data * minimumIndicatorVertical;
		if (contains(out, max.Idx)){
			//top = -1;
			continue;
		}
		// top
		unsigned int cur = max.Idx;
		int top = -1;
		while (true){
			if (cur <= 0 || projValues[cur] < check || contains(out, cur)){
				top = cur;
				break;
			}
			cur--;
		}

		// bottom
		cur = max.Idx;
		int bottom = -1;
		while (true){
			if (cur >= boxWidth + padding - 1 || projValues[cur] < check || contains(out, cur)){
				bottom = cur;
				break;
			}

			cur++;
		}

		/*int width = bottom - top;
		top = std::min(boxWidth + (int)padding - 1, top + (int)(width*leftOffset));
		bottom = std::max(0, bottom - (int)(width*leftOffset));*/
		out.push_back({ top, bottom });
		std::cout << top << "," << bottom << std::endl;

	}
	return out;
}

std::vector<cv::Rect> horizProjection(const std::vector<cv::Rect>& boxes, const Projection& projData,
	const Regions& textRegions, bool initial){
	std::vector<cv::Rect> out;
	for (const auto& box : boxes){
		unsigned int padding = box.height / 3;
		unsigned int paddingTop = std::min((int)padding, box.y);
		unsigned int paddingBottom = std::min((int)padding, frameH - box.y - box.height); // ???
		padding = paddingTop + paddingBottom;
		if (initial == false){
			padding = paddingTop = paddingBottom = 0;
		}
		auto projValues = new unsigned int[box.height + padding];
		auto res = new double[box.height + padding];
		for (int i = 0; i < box.height + padding; i++){
			projValues[i] = projData[box.y + i - paddingTop][box.x + box.width - 1] - projData[box.y + i - paddingTop][box.x];
		}

		meanfilter(projValues, res, box.height + padding, minHeight);
		auto minExtremumVal = minExtremumK * *std::max_element(res + paddingTop, res + paddingTop + box.height); // padding | data | padding
		std::vector<LocalMax> maximus;
		findLocalMax(res, box.height, maximus, minExtremumVal, paddingTop); // only in the real data, not in padding

		std::vector<Interval> intervals = expandingBoxesHorizontal(maximus, box, res, padding, box.height);
		for (const auto& interv : intervals){
			out.push_back(cv::Rect(box.x, box.y + interv.start - paddingTop, box.width, interv.end - interv.start + 1));
		}
		/*CvPlot::plot("RGB", &res[paddingTop], box.height, 1);
		CvPlot::label("B");*/
		delete[] projValues;
		delete[] res;
	}
	return out;
}

std::vector<cv::Rect> verticalProjection(const std::vector<cv::Rect>& boxes, const Projection& projData,
	const Regions& textRegions, bool initial){
	std::vector<cv::Rect> out;
	for (const auto& box : boxes){
		unsigned int padding = box.width / 3;
		unsigned int paddingLeft = std::min((int)padding, box.x);
		unsigned int paddingRight = std::min((int)padding, frameW - box.x - box.width); // ???
		padding = paddingRight + paddingLeft;
		if (initial == false){
			padding = paddingLeft = paddingRight = 0;
		}
		auto projValues = new unsigned int[box.width + padding];
		auto res = new double[box.width + padding];
		for (int i = 0; i < box.width + padding; i++){
			projValues[i] = projData[box.y + box.height - 1][box.x + i - paddingLeft] - projData[box.y][box.x + i - paddingLeft];
		}

		int filterSize = std::min(box.width/3, (int)verticalProjSpaceK*box.height);
		meanfilter(projValues, res, box.width + padding, filterSize);
		auto minExtremumVal = minExtremumK * *std::max_element(res + paddingLeft, res + paddingRight + box.width); // padding | data | padding
		std::vector<LocalMax> maximus;
		findLocalMax(res, box.width, maximus, minExtremumVal, paddingLeft); // only in the real data, not in padding

		std::vector<Interval> intervals = expandingBoxesVertical(maximus, box, res, padding, box.width, box.height);
		for (const auto& interv : intervals){

			out.push_back(cv::Rect(box.x + interv.start - paddingLeft, box.y, interv.end - interv.start + 1, box.height));
		}
		//CvPlot::plot("RGB", &res[paddingLeft], box.width, 1);
		//CvPlot::label("B");
		delete[] projValues;
		delete[] res;
	}
	return out;
}

std::vector<cv::Rect> projectionProcessing(const Projection& horizProj, const Projection& vertProj,
	std::vector<cv::Rect>& initialBoxes, const Regions& textRegions){
	std::vector<cv::Rect> out = horizProjection(initialBoxes, horizProj, textRegions, true);
	out = verticalProjection(out, vertProj, textRegions, true);
	int num = out.size();
	bool odd = true;
	while (true){
		out = odd ? horizProjection(out, horizProj, textRegions, false) : verticalProjection(out, vertProj, textRegions, false);
		if (out.size() == num){
			break;
		}
		num = out.size();
		odd++;
	}
	for (auto& rec : out){
		if (rec.height * 3 > rec.width){
			continue;
		}
		int x2 = rec.x + rec.width - 1;
		if (x2 != frameW - 1){
			x2 = std::max(0, rec.x + rec.width - (int)(rec.height*leftOffset));
		}
		if (rec.x != 0){
			rec.x = std::min(frameW - 1, rec.x + (int)(rec.height*leftOffset));
		}
		rec.width = x2 - rec.x;
	}

	return out;

}

std::vector<cv::Rect> filterRectangles(std::vector<cv::Rect>& out, const Projection& horizProjection){
	std::vector<cv::Rect> v2, v3;
	std::copy_if(out.begin(), out.end(), std::back_inserter(v2),
		[](cv::Rect const & rec) { return rec.height > rectangleZoneHeight / 7 && rectangleZoneHeight / 3  < rec.width; });
	for (const cv::Rect& rec : v2){
		int sum = 0;
		int linesNumber = 0;
		for (int i = rec.y; i < rec.y + rec.height; i++){
			int val = horizProjection[i][rec.x + rec.width - 1] - horizProjection[i][rec.x];
			if (val != 0){
				sum += val;
				linesNumber++;
			}
		}
		double expectation = sum / (double)linesNumber;
		if (expectation*2>rec.width / (double)rec.height){
			v3.push_back(rec);
		}
		/*double sum2 = 0;
		for (int i = rec.y; i < rec.y + rec.height; i++){
			int val = horizProjection[i][rec.x + rec.width - 1] - horizProjection[i][rec.x];
			if (val != 0){
				sum2 += (expectation - val)*(expectation - val);
			}
		}

		std::cout << "dispers" + std::to_string(sum2 / (double)linesNumber) << " " << std::to_string(expectation) << std::endl;*/

	}

	return v3;
}

void framePackProcessing(){
	// last frame in the buffer is out of the pack
	Mat& first = bufferOfFrames.at(0).boolEdges;
	Mat& orientFirst = bufferOfFrames.at(0).edges;
	Mat& orientLast = bufferOfFrames.at(bufferOfFrames.size() - 2).edges;
	Mat packEdges = Mat(first.size(), CV_8U, Scalar::all(0));
	Mat packEdges2 = Mat(first.size(), CV_8U, Scalar::all(0));
	int i = 0;
	for (auto& it = bufferOfFrames.begin(); it != bufferOfFrames.begin() + bufferOfFrames.size() - 1; it++){
		cv::add((*it).boolEdges, packEdges, packEdges);
		i++;
	}

	threshold(packEdges, packEdges2, minAppearances * (bufferOfFrames.size() - 1), 255, cv::THRESH_BINARY);

	auto textRegion = std::vector<std::vector<regionCounter>>(zonesNumberHeight, vector<regionCounter>(zonesNumberWidth));
	textRegionsComposition(textRegion);
	Labels labels = find_components(textRegion);

	auto bBoxes = generateInitialBoundingBoxes(textRegion, labels);

	//std::vector<std::vector<unsigned int>> horizProj(first.rows, std::vector<unsigned int>(first.cols, 0));
	Projection horizProj = new unsigned int*[frameH];
	Projection vertProj = new unsigned int*[frameH];
	for (int i = 0; i < frameH; i++){
		horizProj[i] = new unsigned int[frameW];
		vertProj[i] = new unsigned int[frameW];
	}
	getHorizProjectionData(horizProj, packEdges2, orientFirst, orientLast);
	getVertProjectionData(vertProj, packEdges2, orientFirst, orientLast);

	std::vector<cv::Rect> out = projectionProcessing(horizProj, vertProj, bBoxes, textRegion);

	double t = omp_get_wtime();

	out = filterRectangles(out, horizProj);


	for (int i = 0; i < frameH; i++){
		delete[] horizProj[i];
		delete[] vertProj[i];
	}
	delete[] horizProj;
	delete[] vertProj;

	for (auto& r : out){
		std::cout << r << std::endl;
	}
	RNG rng(312345);
	auto orig = bufferOfFrames.at(bufferOfFrames.size() - 2).orig;
	for (const auto& rec : out){
		cv::Mat roi = orig(rec);
		cv::Mat color(roi.size(), CV_8UC3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
		double alpha = 0.3;
		cv::addWeighted(color, alpha, roi, 1.0 - alpha, 0.0, roi);
	}

	if (debug){
		imshow(std::to_string(outCounter) + "_res_" + std::to_string(bufferOfFrames.size() - 1) + "_labels" + std::to_string(labels.size()) +
			"_rec" + std::to_string(out.size()) + ".jpg", orig);
	}
	else{
		imwrite(outDir + std::to_string(outCounter) + "_res_" + std::to_string(bufferOfFrames.size() - 1) +
			"_labels" + std::to_string(labels.size()) + "_rec" + std::to_string(out.size()) + ".jpg", orig);
	}
	outCounter++;

	//imshow("res" + std::to_string(omp_get_wtime()), orig);
	std::cout << "Time4: " << omp_get_wtime() - t << std::endl;
	//imshow("res3" + std::to_string(omp_get_wtime()), packEdges2);
}

void processing()
{
	setRestrictions();
	std::vector<edgesAndDerivatives> cannyBuffer = std::vector<edgesAndDerivatives>(4);
	int counter = 0;
	for (auto srcImg : lFiles){
		Mat original, detected_edges, dx, dy, gray;
		generateCanny(counter, cannyBuffer);
		detected_edges = cannyBuffer.at(2).edges;
		dx = cannyBuffer.at(2).dx;
		dy = cannyBuffer.at(2).dy;

		original = cannyBuffer.at(2).orig;
		gray = cannyBuffer.at(2).gray;

		// contours
		vector<vector<Point> > contours;
		vector<Vec4i> hierarchy;
		Mat copy = detected_edges.clone();
		findContours(copy, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE, Point(0, 0));

		// contours info and filters
		vector<Contour> contInfo;
		filterContours(contours, contInfo);
		filterUnstatic(contours, contInfo, cannyBuffer);
		//int p = 0;
		//for (auto& cont : contours){
		//	if (contInfo.at(p).actual){
		//		for (auto& cont2 : cont){
		//			if (cont2.x == 402 && cont2.y == 555){
		//				std::cout << "yes";
		//			}
		//		}
		//	}
		//	p++;
		//}
		/// orientation
		Mat  ori;
		phase(dx, dy, ori, true);
		Mat oriMap = orientationMap(ori, detected_edges, /*dir1, dir2, dir3, dir4,*/ contours, contInfo);

		// oriented subcontours
		vector<vector<Point>> contours2;
		vector<vector<vecSize>> relContSubcont(contours.size());
		vector<vecSize> relSubcontCont;
		vector<SubContour> subcInfo;

		double tb = omp_get_wtime();
		findOrientedContours(contours, contours2, oriMap, relContSubcont, relSubcontCont, subcInfo, contInfo);
		std::cout << "Time_before: " << omp_get_wtime() - tb << std::endl;
		subContInfo(subcInfo, contours2);


		// parallel lines filter
		findParallelLines(contours, relContSubcont, relSubcontCont, subcInfo, contInfo, ori, contours2, oriMap);

		// Draw contours
		RNG rng(122345);
		Mat drawing = Mat::zeros(copy.size(), CV_8UC3);
		int m = 0;
		for (int i = 0; i < contours.size(); i++)
		{
			/*if (contInfo.at(relSubcontCont.at(i)).actual == false || contours2.at(i).size() < minSubContourLen){
			continue;
			}*/
			if (contInfo.at(i).actual == false){
				continue;
			}
			Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
			//drawContours(drawing, contours, i, color, 1, 8, hierarchy, 0, Point());
			m++;
		}

		// edges 
		Mat textEdges = Mat(ori.size(), CV_8U, Scalar::all(NOTEDGE));
		Mat boolEdges = Mat(ori.size(), CV_8U, Scalar::all(0));
		contours2TextEdges(contours, contInfo, oriMap, textEdges, boolEdges);


		// text regions edges
		Mat cornersBool = blackM.clone();
		auto  textRegion = std::shared_ptr<std::vector<std::vector<regionCounter>>>(
			new std::vector<std::vector<regionCounter>>(zonesNumberHeight, vector<regionCounter>(zonesNumberWidth)));
		int regNumber = 0;
		findTextRegionsEdges(textEdges,  textRegion, regNumber);


		////
		// corners
		Mat corners = Mat::zeros(gray.size(), CV_32FC1);
		double t3 = omp_get_wtime();
		auto regCopy = *textRegion;
		Labels labels = find_components(regCopy);
		auto bBoxes = generateInitialBoundingBoxes(*textRegion, labels);
		for (auto& box : bBoxes){
			cv::Rect rec = box;
			rec.x = std::max(rec.x - blockSize, 0);
			rec.y = std::max(rec.y - blockSize, 0);
			rec.height = rec.height + std::min(rec.height + blockSize, frameH - rec.height - rec.y);
			rec.width = rec.height + std::min(rec.width + blockSize, frameW - rec.width - rec.x);
			cornersModified(gray(rec), corners(rec), blockSize, kernel_size, dx(rec), dy(rec), kEdges, cv::BORDER_REPLICATE);
		}
		std::cout << "reg: " << omp_get_wtime() - t3 << std::endl;

		// text regions corners
		findTextRegionsCorners(corners, textRegion, cornersBool, regNumber);

		// draw text regions
		for (int i = 0; i < zonesNumberHeight; i++)
		{
			for (int j = 0; j < zonesNumberWidth; j++){
				if (textRegion->at(i).at(j).isText == true){
					/*cv::Mat roi = original(cv::Rect(j*rectangleZoneWidth, i*rectangleZoneHeight, textRegion->at(i).at(j).width, textRegion->at(i).at(j).height));
					cv::Mat color(roi.size(), CV_8UC3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)));
					double alpha = 0.3;
					cv::addWeighted(color, alpha, roi, 1.0 - alpha, 0.0, roi);*/
				}
			}
		}


		std::shared_ptr<std::vector<Point>> interestingPoints(new std::vector<Point>());
		double t2 = omp_get_wtime();
		onlyInterestingPoints(contours, contInfo, interestingPoints, textRegion);
		std::cout << "Time_comp: " << omp_get_wtime() - t2 << std::endl;
		bufferOfFrames.push_back(bufferStruct{ original, textEdges, cornersBool, textRegion, interestingPoints, boolEdges, regNumber });
		// composition of frames
		if (bufferOfFrames.size() > 1){
			auto prevInterestingPoints = bufferOfFrames.at(bufferOfFrames.size() - 2).interestingPoints;
			auto prevTextEdges = bufferOfFrames.at(bufferOfFrames.size() - 2).edges;
			auto prevRegs = bufferOfFrames.at(bufferOfFrames.size() - 2).regions;
			int prevRegNumber = bufferOfFrames.at(bufferOfFrames.size() - 2).regNumber;
			bool ident = compareFrames(interestingPoints, textEdges, textRegion, regNumber, prevInterestingPoints, prevTextEdges, prevRegs, prevRegNumber);
			if (!ident || counter == frames - 1){
				std::cout << "pack" << std::endl;
				double t = omp_get_wtime();
				framePackProcessing();
				std::cout << "Time3: " << omp_get_wtime() - t << std::endl;
				bufferOfFrames.erase(bufferOfFrames.begin(), bufferOfFrames.begin() + bufferOfFrames.size() - 1);
			}

		}

		// show text regions
		if (debug){
			imshow(std::to_string(outCounter) + "_regs_" + std::to_string(regNumber) + ".jpg", original);
		}
		else{
			imwrite(outDir + std::to_string(outCounter) + "_regs_" + std::to_string(regNumber) + ".jpg", original);
		}
		outCounter++;

		cannyBuffer.erase(cannyBuffer.begin());
		counter++;
		std::cout << "Time from start: " << omp_get_wtime() - timer << " frames:" << counter<< std::endl;

	}
}

void initialize(){
	timer = omp_get_wtime();
	/// Load an image
	lFiles = listFilesInDirectory(baseDir);
	filterList(lFiles);
	Mat src1 = imread(baseDir + lFiles.at(0));

	if (!src1.data)
	{
		throw std::invalid_argument("no data");
	}

	frameH = src1.rows;
	frameW = src1.cols;
	frames = lFiles.size();

	processing();

}

/** @function main */
int main(int argc, char** argv)
{
	initialize();
	waitKey(0);


	return 0;
}

